/*
    This file is part of Epoch <http://code.google.com/p/epoch/>.
    
    Copyright (C) 2011  Adam Meily <meily.adam@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include "Protocol.h"
#include <QtCore/QRegExp>
#include <QtCore/QMapIterator>
#include <QtCore/QStringBuilder>
#include "Logger.h"

static Log *mylog = Logger::getLog(__FILE__);

Protocol::Protocol(int socket, Role role, const NodeConfig& config)
        : m_messagePreprocess(true), m_role(role), m_localConfig(config)
{
    bool read = m_socket.bytesAvailable() > 0;
    m_socket.setSocketDescriptor(socket);
    
    connect(&m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
            SLOT(onSocketError(QAbstractSocket::SocketError)));
    
    connect(&m_socket, SIGNAL(readyRead()), SLOT(onReadReady()));
    connect(&m_socket, SIGNAL(encrypted()), SLOT(onEncryptionReady()));
}

Protocol::~Protocol()
{
    if(m_remoteConfig)
        delete m_remoteConfig;
}

int Protocol::handshake()
{
    int r = 0;
    if(m_role == ServerRole)
    {
        Request req;
        req.method(Request::Handshake);
        req.args(m_localConfig.toRecord());
        r = write(req);
    }
    else
    {
        if(!m_readLock && m_socket.bytesAvailable() > 0)
            onReadReady();
    }
    
    return r;
}

Protocol::Role Protocol::role() const
{
    return m_role;
}

const NodeConfig& Protocol::localConfig() const
{
    return m_localConfig;
}

const NodeConfig* Protocol::remoteConfig() const
{
    return m_remoteConfig;
}

bool Protocol::messagePreprocess() const
{
    return m_messagePreprocess;
}

void Protocol::resumeMessagePreprocess()
{
    m_messagePreprocess = true;
}

void Protocol::stopMessagePreprocess()
{
    m_messagePreprocess = false;
}

void Protocol::onEncryptionReady()
{
    onHandshakeComplete();
}

/*
EpochProtocol::Response EpochProtocol::lastResponse() const
{
    return m_lastResponse;
}
*/

void Protocol::onReadReady()
{
    if(m_readLock)
        return;
    
    m_readLock = true;
    while(m_messagePreprocess && m_socket.canReadLine())
    {
        QString buff(m_socket.readLine());
        if(buff.isEmpty())
        {
            if(!m_buffer.isEmpty())
            {
                dispatch();
                m_buffer.clear();
            }
        }
        else
            m_buffer.append(buff);
    }
    
    if(!m_messagePreprocess && m_socket.bytesAvailable() > 0)
        onRawMessage(m_socket.readAll());
    
    m_readLock = false;
}

int Protocol::closeConnection()
{
    m_socket.close();
    return 0;
}

void Protocol::onDisconnectSlot()
{
    onDisconnect();
}


void Protocol::onSocketError(QAbstractSocket::SocketError error)
{
    //if(isConnected())
    closeConnection();
}

int Protocol::dispatch()
{
    if(m_role == ClientRole)
    {
        Request req(m_buffer);
        if(req.method() == Request::Handshake)
        {
            if(m_remoteConfig)
                mylog->debug("Got duplicate handshake request");
            
            if(processHandshake(req) < 0)
            {
                closeConnection();
                return -1;
            }
        }
        else
            onRequest(req);
    }
    else
    {
        Response res(m_buffer);
        if(!m_remoteConfig)
        {
            if(res.status() != Response::Ok)
            {
                mylog->debug("Got invalid handshake response from client");
                closeConnection();
                return -1;
            }
            
            if(processHandshake(res) < 0)
            {
                closeConnection();
                return -1;
            }
        }
        else
            onResponse(res);
    }
    
    return 0;
}

int Protocol::processHandshake(const Message& msg)
{
    m_remoteConfig = NodeConfig::fromRecord(msg.args());
    if(!m_remoteConfig)
    {
        mylog->debug("Invalid handshake message");
        closeConnection();
        return -1;
    }
    
    if(m_role == ClientRole)
    {
        Response res;
        res.status(Response::Ok);
        res.args(m_localConfig.toRecord());
        return write(res) > 0 ? 0 : -1;
    }
    
    return 0;
}


qint64 Protocol::write(const Message& msg)
{
    QByteArray buff;
    QListIterator<Record> i = msg.recordIterator();
    
    buff.reserve(512);
    buff += msg.operation().toUtf8();
    buff += msg.args();
    
    while(i.hasNext())
    {
        buff += '\n';
        buff += i.next();
    }
    
    return write(buff);
}


qint64 Protocol::write(const QByteArray& raw)
{
    return m_socket.write(raw);
}

QByteArray operator+(const QByteArray& buffer, const Record& record)
{
    QByteArray rbuff(buffer);
    rbuff += record;
    return rbuff;
}

QByteArray& operator+=(QByteArray& buffer, const Record& record)
{
    RecordFieldIterator i(record);
    if(buffer[buffer.length()-1] != '\n')
        buffer.append(' ');
    
    bool next = i.hasNext();
    while(next)
    {
        i.next();
        QString val = i.value();
        val.replace('"', "\\\"");
        
        buffer += i.key().toUtf8() + "=\"" +
                  val.toUtf8() + '"';
        
        if((next = i.hasNext()))
            buffer += ' ';
    }
    
    return buffer;
}

